package cloud.agileframework.generator.model;

import cloud.agileframework.common.annotation.Remark;
import cloud.agileframework.common.constant.Constant;
import cloud.agileframework.common.util.clazz.TypeReference;
import cloud.agileframework.common.util.db.DataBaseUtil;
import cloud.agileframework.common.util.object.ObjectUtil;
import cloud.agileframework.common.util.string.StringUtil;
import cloud.agileframework.dictionary.annotation.Dictionary;
import cloud.agileframework.dictionary.annotation.DirectionType;
import cloud.agileframework.generator.model.config.PropertyBaseValue;
import cloud.agileframework.generator.model.config.PropertyConfig;
import cloud.agileframework.generator.model.config.PropertyDicValue;
import cloud.agileframework.generator.properties.AnnotationType;
import cloud.agileframework.spring.util.BeanUtil;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author 佟盟
 * @version 1.0
 * 日期： 2019/2/11 14:18
 * 描述： 表模型信息
 * @since 1.0
 */
@Setter
@Getter
@NoArgsConstructor
public class TableModel extends BaseModel {
    private String moduleName = BeanUtil.getApplicationContext().getId();
    private String tableCat;
    private String tableName;
    private String selfReferencingColName;
    private String tableSchem;
    private String typeSchem;
    private String typeCat;
    private String tableType;

    private String refGeneration;
    private String typeName;

    //根据表明转换出来的各种名字
    private String javaName;
    private String mvcPackageName;
    private String serviceName;
    private String entityName;
    private String entityCenterLineName;
    private String doName;
    private String inVoName;
    private String outVoName;
    private String lowerName;

    //归属种模块名
    private String modelName;

    //各种包名
    private String servicePackageName;
    private String entityPackageName;
    private String voPackageName;
    private String doPackageName;
    private String controllerPackageName;

    private Set<ColumnModel> columns = Sets.newHashSet();
    private Set<ColumnModel> newColumns = Sets.newHashSet();
    private boolean haveSetMethod;
    private boolean haveGetMethod;
    private boolean haveEqualsAndHashCodeMethod = true;

    //外键
    private Set<FImportKeyColumn> fImportKeyColumns;
    private Set<FExportKeyColumn> fExportKeyColumns;

    public void build() {
        //处理导入类信息
        columns.forEach(c -> {
            c.build();
            setImport(c.getImports());
        });
        //处理导入类信息
        newColumns.forEach(c -> {
            c.build();
            setImport(c.getImports());
        });
    }

    public void addColumn(ColumnModel columns) {
        this.columns.add(columns);
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
        this.modelName = tableName.substring(0, tableName.indexOf("_"));
        this.mvcPackageName = tableName.replaceFirst("_bt_", "_").substring(tableName.indexOf("_") + 1).replace("_", "");
        this.javaName = StringUtil.toUpperName(tableName);
        this.lowerName = StringUtil.toLowerName(tableName);

        //处理外键
        fExportKeysHandler(tableName);
        fImportKeysHandler(tableName);

        List<Map<String, Object>> columnInfos = DataBaseUtil.listColumns(getDataSourceProperties().getUrl(),
                getDataSourceProperties().getUsername(),
                getDataSourceProperties().getPassword(),
                tableName);
        for (Map<String, Object> column : columnInfos) {
            ColumnModel columnModel;

            if (PrimaryKeyColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(PrimaryKeyColumn.class, column);
            } else if (DeleteColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(DeleteColumn.class, column);
            } else if (CreateTimeColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(CreateTimeColumn.class, column);
            } else if (UpdateTimeColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(UpdateTimeColumn.class, column);
            } else if (CreateUserColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(CreateUserColumn.class, column);
            } else if (UpdateUserColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(UpdateUserColumn.class, column);
            } else if (ParentKeyColumn.is(column)) {
                columnModel = ObjectUtil.getObjectFromMap(ParentKeyColumn.class, column);
            } else if (FImportKeyColumn.is(column, fImportKeyColumns)) {
                columnModel = ObjectUtil.getObjectFromMap(FImportKeyColumn.class, column);
            } else {
                columnModel = ObjectUtil.getObjectFromMap(ColumnModel.class, column);
            }

            addColumn(columnModel);
        }
        Set<ColumnModel> newC = getColumns().stream()
                .filter(c -> c.getPropertyConfig() != null)
                .map(c -> {
                    PropertyConfig propertyConfig = c.getPropertyConfig();
                    PropertyBaseValue value = propertyConfig.getValue();
                    if (value instanceof PropertyDicValue) {
                        return parseNewColumn(c, value);
                    }
                    return null;
                }).collect(Collectors.toSet());
        this.newColumns.addAll(newC);

        this.serviceName = getProperties().getServicePrefix() + javaName + getProperties().getServiceSuffix();
        this.entityName = getProperties().getEntityPrefix() + javaName + getProperties().getEntitySuffix();
        this.doName = javaName + "Do";
        this.inVoName = javaName + "InVo";
        this.outVoName = javaName + "OutVo";
        this.entityCenterLineName = StringUtil.toUnderline(javaName).replace(Constant.RegularAbout.UNDER_LINE, Constant.RegularAbout.MINUS).toLowerCase();

        if (ArrayUtils.contains(getProperties().getAnnotation(), AnnotationType.JPA) || ArrayUtils.contains(getProperties().getAnnotation(), AnnotationType.VALIDATE)) {
            addAnnotation(Setter.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));
            addAnnotation(Builder.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));
            addAnnotation(ToString.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));
        } else {
            addAnnotation(Data.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));
        }

        addAnnotation(AllArgsConstructor.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));
        addAnnotation(NoArgsConstructor.class, AnnotationType.LOMBOK, desc -> getAnnotationDesc().add(desc));

        addAnnotation(Entity.class, AnnotationType.JPA, desc -> getAnnotationDesc().add(desc));
        addAnnotation(new Table() {

            @Override
            public Class<? extends Annotation> annotationType() {
                return Table.class;
            }

            @Override
            public String name() {
                return toBlank(getTableName());
            }

            @Override
            public String catalog() {
                return toBlank(getTableCat());
            }

            @Override
            public String schema() {
                return toBlank(getTableSchem());
            }

            @Override
            public UniqueConstraint[] uniqueConstraints() {
                return new UniqueConstraint[0];
            }

            @Override
            public Index[] indexes() {
                return new Index[0];
            }
        }, AnnotationType.JPA, desc -> getAnnotationDesc().add(desc));

        this.hibernateAnnotationHandler();

        this.haveSetMethod = !getImports().contains(Setter.class) && !getImports().contains(Data.class);
        this.haveGetMethod = !getImports().contains(Getter.class) && !getImports().contains(Data.class);

        this.setImport(Objects.class);
    }

    private ColumnModel parseNewColumn(ColumnModel c, PropertyBaseValue value) {
        ColumnModel c1 = ObjectUtil.to(c, new TypeReference<DicColumn>() {
        });
        c1.setColumnName(c.getColumnName() + "_name");
        c1.getAnnotationDesc().clear();
        c1.getFieldAnnotationDesc().clear();
        c1.addAnnotation(new Dictionary() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return Dictionary.class;
            }

            @Override
            public String dicCode() {
                return value.getValue();
            }

            @Override
            public String[] fieldName() {
                return new String[]{c.getJavaName()};
            }

            @Override
            public boolean isFull() {
                return false;
            }

            @Override
            public String split() {
                return "/";
            }

            @Override
            public DirectionType directionType() {
                return DirectionType.CODE_TO_NAME;
            }

            @Override
            public String defaultValue() {
                return Constant.AgileAbout.DIC_TRANSLATE_FAIL_NULL_VALUE;
            }

            @Override
            public String dataSource() {
                return Constant.AgileAbout.DIC_DATASOURCE;
            }
        }, AnnotationType.AGILE, desc -> c1.getDicAnnotationDesc().add(desc));
        return c1;
    }

    /**
     * 处理外键
     *
     * @param tableName 表
     */
    private void fExportKeysHandler(String tableName) {
        List<Map<String, Object>> fKeys = DataBaseUtil.listFKeys(getDataSourceProperties().getUrl(),
                getDataSourceProperties().getUsername(),
                getDataSourceProperties().getPassword(),
                tableName);

        Set<String> cache = Sets.newHashSet();

        fKeys.forEach(fk -> {
            FExportKeyColumn columnModel = ObjectUtil.getObjectFromMap(FExportKeyColumn.class, fk);
            if (cache.contains(columnModel.getFktableName())) {
                columnModel.setFktableName(columnModel.getFktableName() + 1);
            }
            cache.add(columnModel.getFktableName());
            addFExportKeyColumns(columnModel);
//            newColumns.add(columnModel);
        });

        fImportKeysHandler(tableName);
    }

    private void fImportKeysHandler(String tableName) {
        List<Map<String, Object>> fImportKeys = DataBaseUtil.listFImportKeys(getDataSourceProperties().getUrl(),
                getDataSourceProperties().getUsername(),
                getDataSourceProperties().getPassword(),
                tableName);


        fImportKeys.forEach(fk -> {
            FImportKeyColumn columnModel = ObjectUtil.getObjectFromMap(FImportKeyColumn.class, fk);
            addFImportKeyColumns(columnModel);
        });
    }

    private void addFImportKeyColumns(FImportKeyColumn columnModel) {
        if (fImportKeyColumns == null) {
            fImportKeyColumns = Sets.newHashSet();
        }
        fImportKeyColumns.add(columnModel);
    }

    private void addFExportKeyColumns(FExportKeyColumn columnModel) {
        if (fExportKeyColumns == null) {
            fExportKeyColumns = Sets.newHashSet();
        }
        fExportKeyColumns.add(columnModel);
    }

    private void hibernateAnnotationHandler() {
        Set<PrimaryKeyColumn> primaryColumns = columns.stream().filter(c -> c instanceof PrimaryKeyColumn).map(c -> (PrimaryKeyColumn) c).collect(Collectors.toSet());
        Set<DeleteColumn> deleteColumns = columns.stream().filter(c -> c instanceof DeleteColumn).map(c -> (DeleteColumn) c).collect(Collectors.toSet());
        if (!deleteColumns.isEmpty()) {
            addAnnotation(new Where() {
                @Override
                public Class<? extends Annotation> annotationType() {
                    return Where.class;
                }

                @Override
                public String clause() {
                    return deleteColumns.stream().map(c -> c.getColumnName() + " = " + c.getNoDelete()).collect(Collectors.joining(" and "));
                }
            }, AnnotationType.HIBERNATE, desc -> getAnnotationDesc().add(desc));
            addAnnotation(new SQLDelete() {
                @Override
                public Class<? extends Annotation> annotationType() {
                    return SQLDelete.class;
                }

                @Override
                public String sql() {
                    String set = deleteColumns.stream().map(c -> c.getColumnName() + " = " + c.getDeleted()).collect(Collectors.joining(", "));
                    String where = primaryColumns.stream().map(c -> c.getColumnName() + " = ?").collect(Collectors.joining(" and "));
                    return String.format("update %s set %s where %s", getTableName(), set, where);
                }

                @Override
                public boolean callable() {
                    return false;
                }

                @Override
                public ResultCheckStyle check() {
                    return ResultCheckStyle.NONE;
                }
            }, AnnotationType.HIBERNATE, desc -> getAnnotationDesc().add(desc));
        }
    }

    @Override
    public void setRemarks(String remarks) {
        super.setRemarks(remarks);
        addAnnotation(new Remark() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return Remark.class;
            }

            @Override
            public String value() {
                return toBlank(getRemarks());
            }

            @Override
            public boolean ignoreCompare() {
                return false;
            }

        }, AnnotationType.REMARK, desc -> getAnnotationDesc().add(desc));
    }
}
